Challenge 25: Verify safety of VecDeque#564
Challenge 25: Verify safety of VecDeque#564Samuelsills wants to merge 1 commit intomodel-checking:mainfrom
Conversation
40ae507 to
10224fd
Compare
Verify all 43 functions listed in the challenge specification. 44 Kani proof harnesses, 0 failures. Resolves model-checking#286 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
10224fd to
2a16efd
Compare
Verification Coverage ReportUnsafe Functions (13/13 — contracts written and verified)
Safe Abstractions (30/30 — absence of UB verified)
UBs Checked (automatic via Kani/CBMC)
Verification Approach
|
There was a problem hiding this comment.
Pull request overview
This PR adds Kani-focused safety contracts (#[requires]) and proof harnesses to validate the safety of a large set of VecDeque operations, and updates the Challenge 25 documentation with a verification summary.
Changes:
- Added
#[requires]contracts (and some#[kani::modifies]annotations) to several internalVecDequeunsafe fns. - Added a substantial
#[cfg(kani)]verification module with#[kani::proof]and#[kani::proof_for_contract]harnesses forVecDeque. - Updated the Challenge 25 markdown with a “Verification Summary” and mapping of functions to harnesses/contracts.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
library/alloc/src/collections/vec_deque/mod.rs |
Adds safety contracts to VecDeque internals and introduces many Kani proof harnesses in mod verify. |
doc/src/challenges/0025-vecdeque.md |
Adds a verification summary section and tables describing covered functions/contracts/harnesses. |
Comments suppressed due to low confidence (1)
library/alloc/src/collections/vec_deque/mod.rs:517
write_iterwrites todst + ifor every element yielded byiterwithout any bounds check, but its#[requires]only enforcesdst < capacity(). The contract should capture the stated safety requirement that the iterator yields no more thancapacity() - dstitems (e.g., via an upper-bound fromsize_hint()or by requiring anExactSizeIterator/TrustedLen), otherwise the contract is unsound and the body can write past the buffer.
/// Writes all values from `iter` to `dst`.
///
/// # Safety
///
/// Assumes no wrapping around happens.
/// Assumes capacity is sufficient: `dst + iter.len() <= capacity`.
#[inline]
#[requires(dst < self.capacity())]
#[cfg_attr(kani, kani::modifies(
core::ptr::slice_from_raw_parts_mut(self.ptr(), self.capacity()),
written
))]
unsafe fn write_iter(
&mut self,
dst: usize,
iter: impl Iterator<Item = T>,
written: &mut usize,
) {
iter.enumerate().for_each(|(i, element)| unsafe {
self.buffer_write(dst + i, element);
*written += 1;
});
| use core::ops::{Index, IndexMut, Range, RangeBounds}; | ||
| use core::{fmt, ptr, slice}; | ||
|
|
||
| use safety::{ensures, requires}; |
There was a problem hiding this comment.
ensures is imported but never used in this module. This will trigger an unused-import warning in builds that lint warnings as errors; consider removing ensures from the import list or adding an #[allow(unused_imports)] on this use if intentional.
| use safety::{ensures, requires}; | |
| use safety::requires; |
| /// Moves an element out of the buffer | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// May only be called if `off < self.capacity()`. | ||
| #[inline] | ||
| #[requires(off < self.capacity())] | ||
| #[cfg_attr(kani, kani::modifies(self))] | ||
| unsafe fn buffer_read(&mut self, off: usize) -> T { | ||
| unsafe { ptr::read(self.ptr().add(off)) } |
There was a problem hiding this comment.
The safety contract for buffer_read is too weak for the operation it performs. ptr::read(self.ptr().add(off)) is UB if the slot at off is uninitialized (even if off < capacity()), so the precondition/docs should also require that off refers to an initialized element (e.g., corresponds to some logical index < self.len).
| /// Writes all values from `iter` to `dst`, wrapping | ||
| /// at the end of the buffer and returns the number | ||
| /// of written values. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// Assumes that `iter` yields at most `len` items. | ||
| /// Assumes capacity is sufficient. | ||
| #[requires(dst < self.capacity() && len <= self.capacity())] | ||
| #[cfg_attr(kani, kani::modifies( | ||
| self, | ||
| core::ptr::slice_from_raw_parts_mut(self.ptr(), self.capacity()) | ||
| ))] | ||
| unsafe fn write_iter_wrapping( |
There was a problem hiding this comment.
write_iter_wrapping’s #[requires] is missing the key safety assumption documented in the comment: that iter yields at most len items. Without that, the calls to write_iter can still write past the end of the ring buffer. Consider expressing an iterator upper bound in the contract (or changing the function to accept an iterator with a known length).
| #[inline] | ||
| #[requires(old_capacity <= self.capacity() && self.len <= old_capacity)] |
There was a problem hiding this comment.
handle_capacity_increase uses old_capacity - self.head and other arithmetic assuming self.head < old_capacity (or old_capacity == 0). The current #[requires] doesn’t constrain self.head relative to old_capacity, so callers satisfying the stated preconditions can still trigger underflow and subsequent out-of-bounds copies. Consider strengthening the contract (and safety docs) to include old_capacity == 0 || self.head < old_capacity.
| #[inline] | |
| #[requires(old_capacity <= self.capacity() && self.len <= old_capacity)] | |
| /// Additionally, `old_capacity == 0` or `self.head < old_capacity` must hold. | |
| #[inline] | |
| #[requires( | |
| old_capacity <= self.capacity() | |
| && self.len <= old_capacity | |
| && (old_capacity == 0 || self.head < old_capacity) | |
| )] |
| | `abort_shrink` | `target_cap <= cap && len <= target_cap && old_head < cap` | `check_abort_shrink` | | ||
| | `rotate_left_inner` | `mid <= self.len() / 2` | `check_rotate_left_inner` | | ||
| | `rotate_right_inner` | `k <= self.len() / 2` | `check_rotate_right_inner` | | ||
|
|
There was a problem hiding this comment.
The verification summary claims "All 13 Part A unsafe functions" were verified, but the table lists 15 functions (it includes rotate_left_inner and rotate_right_inner). Please reconcile the counts/text so the summary matches what’s actually covered.
| Note: The core Part A includes 13 unsafe functions. The two internal helpers | |
| `rotate_left_inner` and `rotate_right_inner` are listed here and verified as | |
| well, but are not counted among those 13 Part A unsafe functions. |
| /// Copies a contiguous block of memory len long from src to dst | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// `src + len` and `dst + len` must not exceed `self.capacity()`. | ||
| #[inline] | ||
| #[requires(len <= self.capacity() && src <= self.capacity() - len && dst <= self.capacity() - len)] | ||
| #[cfg_attr(kani, kani::modifies( | ||
| core::ptr::slice_from_raw_parts_mut(self.ptr().add(dst), len) | ||
| ))] | ||
| unsafe fn copy(&mut self, src: usize, dst: usize, len: usize) { |
There was a problem hiding this comment.
The copy safety contract only constrains src/dst/len to be in-bounds, but ptr::copy(self.ptr().add(src), ...) also requires the source range to be initialized for T (reading uninitialized T is UB). If the intent is to fully formalize the unsafe preconditions, the contract/docs should also state that src..src+len refers to initialized elements in the buffer (and that the destination is valid for writes).
Summary
Verify all 43 functions listed in Challenge 25. 44 Kani proof harnesses, 0 failures.
Part A (13 unsafe functions): Safety contracts and proof_for_contract harnesses for buffer_read, buffer_write, buffer_range, push_unchecked, copy, copy_nonoverlapping, copy_slice, wrap_copy, write_iter, write_iter_wrapping, handle_capacity_increase, from_contiguous_raw_parts_in, abort_shrink.
Part B (30 safe abstractions): Proof harnesses for get, get_mut, swap, reserve, try_reserve, shrink_to, truncate, as_slices, as_mut_slices, range, range_mut, drain, pop_front, pop_back, push_front, push_back, insert, remove, split_off, append, retain_mut, grow, resize_with, make_contiguous, rotate_left, rotate_right, and others.
All harnesses exercise returned values to ensure full unsafe path coverage.
Resolves #286
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.